﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using Gumini.JulaDB.Data;
using Gumini.JulaDB.DataTypes;
using Gumini.JulaDB.Engine;

namespace Gumini.JulaDB.Provider
{
    [System.ComponentModel.DesignerCategory("")]
    public class JulaDbDataReader : DbDataReader
    {
        private bool _isOpened = true;
        private readonly IList<RecordSet> _resultTables;
        private int _currentRecordSetIndex = 0;
        private int _currentResultIndex = -1; // position of result within a result set

        private void CheckOpened()
        {
            if (!_isOpened)
                throw new InvalidOperationException("JulaDbDataReader is not opened.");
        }

        private RecordSet CurrentTable
        {
            get { return _resultTables[_currentRecordSetIndex]; }
        }

        private Row CurrentRow
        {
            get { return CurrentTable.Rows[_currentResultIndex]; }
        }

        internal JulaDbDataReader(IList<RecordSet> resultTables)
        {
            _resultTables = resultTables;
        }

        /// <summary>
        /// Closes the DataReader object.
        /// </summary>
        public override void Close()
        {
            CheckOpened();
            _isOpened = false;
        }

        /// <summary>
        /// This will always return 0.
        /// </summary>
        public override int Depth
        {
            get { return 0; }
        }

        public override int FieldCount
        {
            get
            {
                CheckOpened();
                return CurrentTable.Heading.NumberOfColumns;
            }
        }

        public override bool GetBoolean(int ordinal)
        {
            CheckOpened();
            return (bool)CurrentRow[ordinal];
        }

        public override byte GetByte(int ordinal)
        {
            CheckOpened();
            return (byte)CurrentRow[ordinal];
        }

        public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
        {
            CheckOpened();
            var sourceArray = (byte[])CurrentRow[ordinal];
            Array.Copy(sourceArray, dataOffset, buffer, bufferOffset, length);
            return length;
        }

        public override char GetChar(int ordinal)
        {
            CheckOpened();
            return (char)CurrentRow[ordinal];
        }
        
        public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
        {
            var sourceArray = ((string)CurrentRow[ordinal]).ToCharArray();
            Array.Copy(sourceArray, dataOffset, buffer, bufferOffset, length);
            return length;
        }

        public override DateTime GetDateTime(int ordinal)
        {
            CheckOpened();
            return (DateTime)CurrentRow[ordinal];
        }

        public override decimal GetDecimal(int ordinal)
        {
            CheckOpened();
            return (decimal)CurrentRow[ordinal];
        }

        public override double GetDouble(int ordinal)
        {
            CheckOpened();
            return (double)CurrentRow[ordinal];
        }

        public override float GetFloat(int ordinal)
        {
            CheckOpened();
            return (float)CurrentRow[ordinal];
        }

        public override Guid GetGuid(int ordinal)
        {
            throw new NotSupportedException("GUIDs are not supported");
        }

        public override short GetInt16(int ordinal)
        {
            CheckOpened();
            return (short)CurrentRow[ordinal];
        }

        public override int GetInt32(int ordinal)
        {
            CheckOpened();
            return (int)CurrentRow[ordinal];
        }

        public override long GetInt64(int ordinal)
        {
            CheckOpened();
            return (long)CurrentRow[ordinal];
        }

        public override string GetString(int ordinal)
        {
            CheckOpened();
            return (string)CurrentRow[ordinal];
        }

        public override object GetValue(int ordinal)
        {
            CheckOpened();
            return CurrentRow[ordinal];
        }

        public override int GetValues(object[] values)
        {
            CheckOpened();
            int numberOfValues = Math.Min(this.FieldCount, values.Length);
            for (int i = 0; i < numberOfValues; i++)
                values[i] = GetValue(i);
            return numberOfValues;
        }

        public override bool IsDBNull(int ordinal)
        {
            CheckOpened();
            return CurrentRow[ordinal] == null;
        }

        public override object this[string name]
        {
            get
            {
                CheckOpened();
                int ordinal = GetOrdinal(name);
                return CurrentRow[ordinal];
            }
        }

        public override object this[int ordinal]
        {
            get
            {
                CheckOpened();
                return CurrentRow[ordinal];
            }
        }

        public override int GetOrdinal(string name)
        {
            CheckOpened();
            return CurrentTable.Heading.GetColumnOrdinal(name);
        }

        public override string GetDataTypeName(int ordinal)
        {
            CheckOpened();
            var type = CurrentTable.Heading[ordinal].Type ?? DataType.Varchar;
            return type.Names.First();
        }

        public override Type GetFieldType(int ordinal)
        {
            CheckOpened();
            var type = CurrentTable.Heading[ordinal].Type ?? DataType.Varchar;
            return type.ManagedType;
        }

        public override DataTable GetSchemaTable()
        {
            throw new NotSupportedException("GetSchemaTable() is not supported");
        }

        /// <summary>
        /// Will always return -1, as JulaDbDataReader supports only SELECT statement.
        /// </summary>
        public override int RecordsAffected
        {
            get { return -1; }
        }

        public override string GetName(int ordinal)
        {
            CheckOpened();
            return CurrentTable.Heading[ordinal].ColumnName;
        }

        /// <summary>
        /// Tre if at least one row is contained within the current table.
        /// </summary>
        public override bool HasRows
        {
            get
            {
                CheckOpened();
                return CurrentTable.Rows.Any();
            }
        }

        public override bool IsClosed
        {
            get { return !_isOpened; }
        }

        /// <summary>
        /// Advances the reader to the next statement.
        /// </summary>
        public override bool NextResult()
        {
            CheckOpened();
            if (_currentRecordSetIndex + 1 < _resultTables.Count)
            {
                _currentRecordSetIndex++;
                return true;
            }
            else
                return false;
        }

        /// <summary>
        /// Advances the reader to the next record in a result set.
        /// </summary>
        public override bool Read()
        {
            CheckOpened();
            return ++_currentResultIndex < CurrentTable.Rows.Count;
        }

        public override IEnumerator GetEnumerator()
        {
            return ((IEnumerator)new DbEnumerator(this));
        }
    }
}
